package cn.assumejson.springframework.aop.aspectj;

import cn.assumejson.springframework.aop.ClassFilter;
import cn.assumejson.springframework.aop.IntroductionAwareMethodMatcher;
import cn.assumejson.springframework.aop.MethodMatcher;
import cn.assumejson.springframework.aop.support.AbstractExpressionPointcut;
import cn.assumejson.springframework.beans.factory.BeanFactory;
import cn.assumejson.springframework.beans.factory.BeanFactoryAware;
import cn.assumejson.springframework.beans.factory.BeansException;
import cn.hutool.core.util.ClassLoaderUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ObjectUtil;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

/**
 * 切点表达式
 *
 * @author Mr. Li
 * @version 1.0
 * @since 1.0 2021/12/2 13:23
 */
public class AspectJExpressionPointcut extends AbstractExpressionPointcut implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {

    private BeanFactory beanFactory;

    private static final Set<PointcutPrimitive> SUPPORTED_PRIMITIVES = new HashSet<>();

    static {
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

    private transient PointcutExpression pointcutExpression;

    private transient ClassLoader pointcutClassLoader;

    public AspectJExpressionPointcut(String expression) {
        PointcutParser pointcutParser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                        SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());

        pointcutExpression = pointcutParser.parsePointcutExpression(expression);
    }

    public BeanFactory getBeanFactory() {
        return beanFactory;
    }

    private Class<?> pointcutDeclarationScope;

    private String[] pointcutParameterNames;
    private Class<?>[] pointcutParameterTypes;

    /**
     * Create a new default AspectJExpressionPointcut.
     */
    public AspectJExpressionPointcut() {
    }

    public AspectJExpressionPointcut(Class<?> declarationScope, String[] paramNames, Class<?>[] paramTypes) {
        this.pointcutDeclarationScope = declarationScope;
        if (paramNames.length != paramTypes.length) {
            throw new IllegalStateException(
                    "Number of pointcut parameter names must match number of pointcut parameter types");
        }
        // 一定不可能为空
        if (pointcutExpression == null) {
            PointcutParser pointcutParser = PointcutParser
                    .getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
                            SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
            String expression = getExpression();
            pointcutExpression = pointcutParser.parsePointcutExpression(expression);
        }
        this.pointcutParameterNames = paramNames;
        this.pointcutParameterTypes = paramTypes;
    }

    @Override
    public String getExpression() {
        String expression = null;
        // 根据Class 类型表达式
        for (Method method : this.pointcutDeclarationScope.getMethods()) {
            if (ObjectUtil.isNotNull(method.getAnnotation(Pointcut.class))) {
                Pointcut annotation = method.getAnnotation(Pointcut.class);
                expression = annotation.value();
            }
        }

        return expression;
    }

    @Override
    public ClassFilter getClassFilter() {
        return this;
    }

    @Override
    public MethodMatcher getMethodMatcher() {
        return this;
    }

    @Override
    public boolean matches(Class<?> clazz) {
        return pointcutExpression.couldMatchJoinPointsInType(clazz);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }


    /**
     * Check whether this pointcut is ready to match,
     * lazily building the underlying AspectJ pointcut expression.
     */
    private PointcutExpression obtainPointcutExpression() {
        if (getExpression() == null) {
            throw new IllegalStateException("Must set property 'expression' before attempting to match");
        }
        if (this.pointcutExpression == null) {
            this.pointcutClassLoader = determinePointcutClassLoader();
            this.pointcutExpression = buildPointcutExpression(this.pointcutClassLoader);
        }
        return this.pointcutExpression;
    }

    private PointcutExpression buildPointcutExpression(ClassLoader pointcutClassLoader) {

        return null;
    }

    private ClassLoader determinePointcutClassLoader() {
        return ClassLoaderUtil.getClassLoader();
    }

    @Override
    public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
        // 获取判断表达式
        obtainPointcutExpression();

        return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
    }
}
